/**
 * StarManager.java
 *
 * Roslan's Remake of MOO in 3D using Ardor3D
 * (C) 2010-2012 Roslan Amir. All rights reserved.
 */

package name.roslan.games.moo3d;

import java.awt.Point;
import java.util.ArrayList;

import name.roslan.games.moo3d.data.Banner;
import name.roslan.games.moo3d.data.Colony;
import name.roslan.games.moo3d.data.Race;
import name.roslan.games.moo3d.data.Star;
import name.roslan.games.moo3d.spatial.StarNode;
import name.roslan.games.moo3d.utils.HexGridUtils;

import com.ardor3d.bounding.BoundingBox;
import com.ardor3d.image.Texture;
import com.ardor3d.math.ColorRGBA;
import com.ardor3d.math.MathUtils;
import com.ardor3d.math.Vector2;
import com.ardor3d.math.Vector3;
import com.ardor3d.math.type.ReadOnlyColorRGBA;
import com.ardor3d.renderer.state.MaterialState;
import com.ardor3d.renderer.state.MaterialState.ColorMaterial;
import com.ardor3d.renderer.state.TextureState;
import com.ardor3d.scenegraph.Mesh;
import com.ardor3d.scenegraph.hint.DataMode;
import com.ardor3d.scenegraph.shape.Sphere;
import com.ardor3d.scenegraph.shape.Sphere.TextureMode;
import com.ardor3d.util.TextureManager;

/**
 * This class manages the <code>StarNode</code> specific attributes and methods.
 * 
 * @author Roslan Amir
 * @version 1.0 - May 3, 2012
 */
public class StarManager {

    /**
     * Default no-arg constructor. Private to prevent instatiation.
     */
    private StarManager() {
    }

    /**
     * The color DARK_YELLOW
     */
    public static final ReadOnlyColorRGBA COLOR_DARK_YELLOW = new ColorRGBA(0.5f, 0.5f, 0f, 1f);

    /**
     * The color DARK_GREEN
     */
    public static final ReadOnlyColorRGBA COLOR_DARK_GREEN = new ColorRGBA(0f, 0.5f, 0f, 1f);

    /**
     * The color DARK_RED
     */
    public static final ReadOnlyColorRGBA COLOR_DARK_RED = new ColorRGBA(0.5f, 0f, 0f, 1f);

    /**
     * The color DARK_WHITE
     */
    public static final ReadOnlyColorRGBA COLOR_DARK_WHITE = new ColorRGBA(0.5f, 0.5f, 0.5f, 1f);

    /**
     * The color DARK_BLUE
     */
    public static final ReadOnlyColorRGBA COLOR_DARK_BLUE = new ColorRGBA(0f, 0f, 0.5f, 1f);

    /**
     * The color DARK_MAGENTA
     */
    public static final ReadOnlyColorRGBA COLOR_DARK_MAGENTA = new ColorRGBA(0.5f, 0f, 0.5f, 1f);

    /**
     * The different colors for the stars - outside scanner range.
     */
    public static final ReadOnlyColorRGBA[] STAR_COLORS_DARK = { COLOR_DARK_YELLOW, COLOR_DARK_GREEN, COLOR_DARK_RED, COLOR_DARK_WHITE, COLOR_DARK_BLUE, COLOR_DARK_MAGENTA };

    /**
     * The different colors for the stars - within scanner range.
     */
    public static final ReadOnlyColorRGBA[] STAR_COLORS_BRIGHT = { ColorRGBA.YELLOW, ColorRGBA.GREEN, ColorRGBA.RED, ColorRGBA.WHITE, ColorRGBA.BLUE, ColorRGBA.MAGENTA };

    /**
     * The sigleton instance of this class.
     */
    private static final StarManager instance = new StarManager();

    /**
     * The radius of the Sphere used to represent a star.
     */
    private static final double STAR_SPHERE_RADIUS = 0.5;

    /**
     * The texture to use to indicate a colonized star.
     */
    private static final String IMAGE_COLONY_TEXTURE = "images/earth_640_320.jpg";

    /**
     * The Mesh to represent an uncolonized star.
     */
    private static final Sphere baseStarMesh;

    /**
     * The Mesh to represent a colonized star.
     */
    private static final Sphere baseColonyMesh;

    /*
     * Create the Sphere object representing each colored star.
     */
    static {
        /*
         * Create colored spheres as the model for the various colored stars. Also used as the Mesh for Orion.
         */
        baseStarMesh = new Sphere("BaseStarSphere", Vector3.ZERO, 64, 64, STAR_SPHERE_RADIUS, TextureMode.Linear);
        baseStarMesh.setModelBound(new BoundingBox());
        baseStarMesh.getSceneHints().setDataMode(DataMode.VBO);
        baseStarMesh.updateModelBound();

        /*
         * Create a textured sphere as the model for the colonies. Also used as the Mesh for the player's home colony.
         */
        baseColonyMesh = (Sphere) baseStarMesh.makeCopy(true);
        baseColonyMesh.setName("BaseColonySphere");
        baseColonyMesh.updateModelBound();

        final TextureState textureState = new TextureState();
        textureState.setTexture(TextureManager.load(IMAGE_COLONY_TEXTURE, Texture.MinificationFilter.Trilinear, true));
        baseColonyMesh.setRenderState(textureState);

        final MaterialState materialState = new MaterialState();
        materialState.setColorMaterial(ColorMaterial.Emissive);
        baseColonyMesh.setRenderState(materialState);
    }

    /**
     * Returns the singleton instance of this class.
     * 
     * @return the singleton instance of this class
     */
    public static StarManager getInstance() {
        return instance;
    }

    /**
     * Create all the stars in the galaxy. The first star created is Orion. Then the player's home star, followed by each of the AI players' home
     * stars. Then the remaining unexplored stars are created.
     */
    public void createStars() {
        /*
         * Initialize galaxy bitmap. Whenever a star is placed, the tile and its surrounding 2 layers of tiles are marked as unavailable. This means
         * each star is at least 3 tiles away from every other star.
         */
        boolean[][] bitmap = new boolean[GameManager.getInstance().galaxyWidth][GameManager.getInstance().galaxyHeight];

        /*
         * Create the node representing Orion and add it to the list of nodes representing stars.
         */
        createOrionNode(bitmap);

        /*
         * The galaxy is divided into 3 by 2 sectors. Each home system will be placed in a sector. Depending on the number of opponents selected, some
         * sectors will be unoccupied.
         */
        ArrayList<Point> homePoints = new ArrayList<Point>(6); // maximum of 6 home worlds (player + 5 AI races)
        int sectorWidth = GameManager.getInstance().galaxyWidth / 3;
        int sectorHeight = GameManager.getInstance().galaxyHeight / 2;
        int minCol = 2;
        int maxCol = sectorWidth - 3;
        int minRow, maxRow, x, y;
        for (int col = 0; col < 3; col++) {
            minRow = 2;
            maxRow = sectorHeight - 3;
            for (int row = 0; row < 2; row++) {
                do {
                    x = MathUtils.nextRandomInt(minCol, maxCol);
                    y = MathUtils.nextRandomInt(minRow, maxRow);
                } while (bitmap[x][y]);
                homePoints.add(new Point(x, y));
                minRow += sectorHeight;
                maxRow += sectorHeight;
            }
            minCol += sectorWidth;
            maxCol += sectorWidth;
        }

        /*
         * Create the node representing the player's home world and add it to the list of nodes representing stars.
         */
        GameManager.getInstance().playerHomeStarNode = createPlayerHomeStarNode(bitmap, homePoints);

        /*
         * Create the node representing each AI player's home world and add them to the list of nodes representing stars.
         */
        createAiHomeStarNodes(bitmap, homePoints);

        /*
         * Randomly place the rest of the stars on the galaxy map.
         */
        createRandomStarNodes(bitmap);
    }

    /**
     * Create the <code>StarNode</code> object representing the Orion star system.
     * 
     * @param bitmap the bitmap array
     */
    private void createOrionNode(boolean[][] bitmap) {
        /*
         * Create and place the star Orion randomly somewhere in the galaxy. Do not place the star along each of the 4 edges.
         */
        int x = MathUtils.nextRandomInt(2, GameManager.getInstance().galaxyWidth - 3);
        int y = MathUtils.nextRandomInt(2, GameManager.getInstance().galaxyHeight - 3);
        updateBitmap(bitmap, x, y);

        /*
         * Create the Star object representing Orion
         */
        final Star orion = Star.createOrion();

        /*
         * Create the StarNode object representing Orion and place it in the galaxy scene graph
         */
        final StarNode orionNode = new StarNode(orion, x, y);
        placeStarNode(orionNode);

        /*
         * Use the base Mesh for an uncolonized star as Orion's Mesh
         */
        baseStarMesh.setName("Star<" + orion.name + '>');
        orionNode.attachChild(baseStarMesh);

        final MaterialState renderState = new MaterialState();
        renderState.setAmbient(STAR_COLORS_DARK[orion.colorIndex]);
        baseStarMesh.setRenderState(renderState);

        GameManager.getInstance().starList.add(orionNode);
        GameManager.getInstance().rootNode.attachChild(orionNode);
    }

    /**
     * Create the <code>StarNode</code> object representing the player home star system.
     * 
     * @param bitmap the bitmap array
     * @param homePoints the list of available home star coordinates
     * @return the <code>StarNode</code> object representing the player home star system
     */
    private StarNode createPlayerHomeStarNode(boolean[][] bitmap, ArrayList<Point> homePoints) {
        /*
         * Randomly extract one tile from the list and update the bitmap array
         */
        final Point point = homePoints.remove(MathUtils.nextRandomInt(0, homePoints.size() - 1));
        updateBitmap(bitmap, point.x, point.y);

        /*
         * Create the Colony object representing the player home world
         */
        final Colony colony = Colony.createHomeWorld(GameManager.getInstance().playerRace, GameManager.getInstance().difficultyIndex); // 0=Easy, 1=Normal, 2=Hard
        colony.name = GameManager.getInstance().playerHomeStarName; // replaces the default name with the user specified name

        /*
         * Create the StarNode object representing the player home world and place it in the galaxy scene graph
         */
        final StarNode starNode = new StarNode(colony, point.x, point.y);
        placeStarNode(starNode);

        /*
         * Update the range of Orion relative to the player home star
         */
        final StarNode orionNode = GameManager.getInstance().starList.get(0); // only 1 StarNode in the list: orion
        orionNode.setRange(starNode);

        /*
         * Use the base Mesh for a colony as the player's home world Mesh
         */
        baseColonyMesh.setName(GameManager.getInstance().playerHomeStarName);
        starNode.attachChild(baseColonyMesh);

        /*
         * Add the Colony name to this star node.
         */
        starNode.addStarNameText(Banner.BANNER_COLORS[GameManager.getInstance().playerBannerIndex]);
        starNode.addStarBillboard();

        /*
         * Add the scanner circle around this colony.
         */
//        final ScannerCircle circle = new ScannerCircle(GameManager.getInstance().playerRace.scannerRange, GameManager.getInstance().BANNER_COLORS[GameManager.getInstance().playerBannerIndex]);
//        starNode.attachChild(circle);

        GameManager.getInstance().starList.add(starNode);
        GameManager.getInstance().rootNode.attachChild(starNode);

        return starNode;
    }

    /**
     * Create the <code>StarNode</code> object representing each AI player's home star system.
     * 
     * @param bitmap the bitmap array
     * @param homePoints the list of available home star coordinates
     */
    private void createAiHomeStarNodes(boolean[][] bitmap, ArrayList<Point> homePoints) {
        for (int index = 1; index < GameManager.getInstance().races.length; index++) { // index 0 = player race
            /*
             * Randomly extract one tile from the list and update the bitmap array
             */
            final Point point = (homePoints.size() == 1) ? homePoints.get(0) : homePoints.remove(MathUtils.nextRandomInt(0, homePoints.size() - 1)); // randomly extract one tile from the list
            updateBitmap(bitmap, point.x, point.y);

            /*
             * Create the Colony object representing the AI player home world
             */
            final Race race = GameManager.getInstance().races[index];
            final Colony colony = Colony.createHomeWorld(race, GameManager.getInstance().difficultyIndex); // 0=Easy, 1=Normal, 2=Hard

            /*
             * Create the StarNode object representing the AI player home world and place it in the galaxy scene graph
             */
            final StarNode starNode = new StarNode(colony, point.x, point.y);
            placeStarNode(starNode);

            /*
             * Update the range of the AI player home world relative to the player home star
             */
            starNode.setRange(GameManager.getInstance().playerHomeStarNode);

            /*
             * Make a copy of the star base Mesh for the player's home world Mesh
             */
            final Mesh mesh = baseStarMesh.makeCopy(true);
            mesh.updateModelBound();
            mesh.setName("Star<" + colony.name + '>');
            starNode.attachChild(mesh);

            final MaterialState renderState = new MaterialState();
            renderState.setAmbient(STAR_COLORS_DARK[colony.colorIndex]);
            mesh.setRenderState(renderState);

            GameManager.getInstance().starList.add(starNode);
            GameManager.getInstance().rootNode.attachChild(starNode);
        }
    }

    /**
     * Randomly create and place the rest of the stars on the galaxy map. The number of stars depends on the difficulty selected.
     *
     * @param bitmap the bitmap array
     */
    private void createRandomStarNodes(boolean[][] bitmap) {
        int minCol = 2;
        int maxCol = GameManager.getInstance().galaxyWidth - 3;
        int minRow = 2;
        int maxRow = GameManager.getInstance().galaxyHeight - 3;
        for (int index = GameManager.getInstance().raceCount + 1; index < GameManager.getInstance().starCount; index++) { // exclude orion + player star + AI races stars
            /*
             * Create and place the star randomly in the galaxy. Do not place the star along each of the 4 edges or within 2 parsecs of existing stars.
             */
            int x, y;
            do {
                x = MathUtils.nextRandomInt(minCol, maxCol);
                y = MathUtils.nextRandomInt(minRow, maxRow);
            } while (bitmap[x][y]);
            updateBitmap(bitmap, x, y);

            /*
             * Create the Star object representing the random uncolonized star
             */
            final Star star = Star.createRandomStar();

            /*
             * Create the StarNode object representing the star and place it in the galaxy scene graph
             */
            final StarNode starNode = new StarNode(star, x, y);
            placeStarNode(starNode);

            /*
             * Update the range of the star relative to the player home star
             */
            starNode.setRange(GameManager.getInstance().playerHomeStarNode);

            /*
             * Make a copy of the star base Mesh for the star Mesh
             */
            final Mesh mesh = baseStarMesh.makeCopy(true);
            mesh.updateModelBound();
            mesh.setName("Star<" + star.name + ">");
            starNode.attachChild(mesh);

            ReadOnlyColorRGBA color = (starNode.range <= GameManager.getInstance().playerRace.scannerRange) ? STAR_COLORS_BRIGHT[star.colorIndex] : STAR_COLORS_DARK[star.colorIndex];
            final MaterialState renderState = new MaterialState();
            renderState.setAmbient(color);
            mesh.setRenderState(renderState);

            GameManager.getInstance().starList.add(starNode);
            GameManager.getInstance().rootNode.attachChild(starNode);
        }
    }

    /**
     * Once a star is placed, mark its tile and the surrounding 2 layers of tiles as being unavailable. This means each star is at least 3 tiles away
     * from every other star.
     * 
     * @param bitmap - the bitmap array
     * @param x - the x-coordinate of the star
     * @param y - the y-coordinate of the star
     */
    private void updateBitmap(boolean[][] bitmap, int x, int y) {
        bitmap[x][y] = true;

        /*
         * 1 parsec around.
         */
        bitmap[x - 1][y + 1] = true;
        bitmap[x][y + 1] = true;
        bitmap[x + 1][y] = true;
        bitmap[x + 1][y - 1] = true;
        bitmap[x][y - 1] = true;
        bitmap[x - 1][y] = true;

        /*
         * 2 parsecs around.
         */
        bitmap[x - 2][y] = true;
        bitmap[x - 2][y + 1] = true;
        bitmap[x - 2][y + 2] = true;
        bitmap[x - 1][y + 2] = true;
        bitmap[x - 1][y - 1] = true;
        bitmap[x][y + 2] = true;
        bitmap[x][y - 2] = true;
        bitmap[x + 1][y + 1] = true;
        bitmap[x + 1][y - 2] = true;
        bitmap[x + 2][y] = true;
        bitmap[x + 2][y - 1] = true;
        bitmap[x + 2][y - 2] = true;
    }

    /**
     * Place this star node in the scene graph (sets the translation of the <code>StarNode</code>).
     * 
     * @param starNode the <code>StarNode</code> to place in the scene
     */
    private void placeStarNode(StarNode starNode) {
        final Vector2 origin = Vector2.fetchTempInstance();
        HexGridUtils.tileToVector2(starNode.tile.x, starNode.tile.y, origin);
        starNode.setTranslation(origin.getX(), origin.getY(), 0);
        Vector2.releaseTempInstance(origin);
    }

}
